package cn.iocoder.yudao.module.lfd.service.appointment;

import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.lfd.controller.admin.appointment.vo.*;
import cn.iocoder.yudao.module.lfd.convert.appointment.AppointmentConvert;
import cn.iocoder.yudao.module.lfd.dal.dataobject.appointment.AppointmentDO;
import cn.iocoder.yudao.module.lfd.dal.dataobject.appointment.AppointmentListDO;
import cn.iocoder.yudao.module.lfd.dal.dataobject.appointment.AppointmentStatictis;
import cn.iocoder.yudao.module.lfd.dal.dataobject.appointmentitem.AppointmentItemDO;
import cn.iocoder.yudao.module.lfd.dal.dataobject.appointmentitem.AppointmentItemList;
import cn.iocoder.yudao.module.lfd.dal.dataobject.barber.BarberDO;
import cn.iocoder.yudao.module.lfd.dal.mysql.appointment.AppointmentMapper;
import cn.iocoder.yudao.module.lfd.dal.mysql.appointmentitem.AppointmentItemMapper;
import cn.iocoder.yudao.module.lfd.service.barber.BarberService;
import cn.iocoder.yudao.module.lfd.service.customer.CustomerService;
import cn.iocoder.yudao.module.lfd.service.number.NumberService;
import cn.iocoder.yudao.module.lfd.service.points.PointsService;
import cn.iocoder.yudao.module.lfd.service.productitem.ProductItemService;
import cn.iocoder.yudao.module.lfd.service.wallet.WalletService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.lfd.enums.ErrorCodeConstants.APPOINTMENT_NOT_EXISTS;

/**
 * 预约 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
public class AppointmentService {

    @Resource
    private AppointmentMapper appointmentMapper;
    @Autowired
    private AppointmentItemMapper appointmentItemMapper;
    @Resource
    private ProductItemService productItemService;
    @Autowired
    private NumberService numberService;
    @Autowired
    private BarberService barberService;
    @Resource
    private PointsService pointsService;
    @Resource
    private CustomerService customerService;
    @Resource
    private WalletService walletService;

    public Long createAppointment(AppointmentCreateReqVO createReqVO) {
        // 插入
        AppointmentDO appointment = AppointmentConvert.INSTANCE.convert(createReqVO);
        appointment.setHairDate(LocalDate.now());
        appointmentMapper.insert(appointment);
        // 返回
        return appointment.getId();
    }


    public void updateAppointment(AppointmentUpdateReqVO updateReqVO) {
        // 校验存在
        validateAppointmentExists(updateReqVO.getId());
        // 更新
        AppointmentDO updateObj = AppointmentConvert.INSTANCE.convert(updateReqVO);
        appointmentMapper.updateById(updateObj);
    }

    public void updateAppointment(AppointmentDO appointmentDO) {
        validateAppointmentExists(appointmentDO.getId());
        appointmentMapper.updateById(appointmentDO);
    }

    public void deleteAppointment(Long id) {
        // 校验存在
        validateAppointmentExists(id);
        // 删除
        appointmentMapper.deleteById(id);
    }

    private void validateAppointmentExists(Long id) {
        if (appointmentMapper.selectById(id) == null) {
            throw exception(APPOINTMENT_NOT_EXISTS);
        }
    }


    public AppointmentDO getAppointment(Long id) {
        AppointmentDO appointment = appointmentMapper.selectById(id);
        if (appointment == null) {
            throw exception(APPOINTMENT_NOT_EXISTS);
        }
        return appointment;
    }


    public List<AppointmentDO> getAppointmentList(Collection<Long> ids) {
        return appointmentMapper.selectBatchIds(ids);
    }


    public List<AppointmentDO> getAppointmentList(AppointmentExportReqVO exportReqVO) {
        return appointmentMapper.selectList(exportReqVO);
    }

    public List<AppointmentDO> getAppointmentList(String openId, AppointmentEnum status) {
        QueryWrapper<AppointmentDO> query = new QueryWrapper<>();
        query.eq("openid", openId);
        query.in("status", status == AppointmentEnum.CANCEL ? Lists.newArrayList(AppointmentEnum.CANCEL, AppointmentEnum.TIMEOUT) : Arrays.asList(status));
        query.orderByDesc("update_time");
        return appointmentMapper.selectList(query);
    }

    public Long waitTime(Long shopId, Long barberId) {
        List<AppointmentDO> appointmentDOS = appointmentMapper.selectByToDayStatus(shopId, barberId, Arrays.asList(AppointmentEnum.WAIT, AppointmentEnum.BARBERING));
        return caculatewaitTime(appointmentDOS);
    }


    public Long waitTime(Long appointmenId) {
        QueryWrapper<AppointmentDO> query = new QueryWrapper<>();
        query.eq("hair_date", LocalDate.now());
        query.lt("id", appointmenId);
        query.in("status", AppointmentEnum.WAIT, AppointmentEnum.BARBERING);
        List<AppointmentDO> appointmentDOS = appointmentMapper.selectList(query);
        Long time = caculatewaitTime(appointmentDOS);
        return time;
    }

    @NotNull
    private Long caculatewaitTime(List<AppointmentDO> appointmentDOS) {
        Long time = 0L;
        for (AppointmentDO appointment : appointmentDOS) {
            if (AppointmentEnum.WAIT == appointment.getStatus()) {
                time = time + appointment.getCostTime();
                continue;
            }
            //服务中 需要的总时间-（现在时间-开始理发时间）
            if (AppointmentEnum.BARBERING == appointment.getStatus()) {
                time = time + appointment.remaiTime();
            }
        }
        return time;
    }


    @Transactional
    public void appointment(String openId, Long shopId, Long barberId, List<Long> itemIds) {
        AppointmentItemList appointmentItems = new AppointmentItemList(productItemService, itemIds);
        AppointmentDO appointmentDO = new AppointmentDO();
        appointmentDO.setShopId(shopId);
        appointmentDO.setStatus(AppointmentEnum.WAIT);
        appointmentDO.setOpenid(openId);
        appointmentDO.setMoney(appointmentItems.money());
        appointmentDO.setCostTime(appointmentItems.costTime());
        appointmentDO.setIntegral(appointmentItems.sumIntegral());
        appointmentDO.setBarberId(barberId);
        appointmentDO.setNumber(getNumber(barberId));
        appointmentDO.setHairDate(LocalDate.now());
        appointmentDO.setItems(appointmentItems.items());
        appointmentMapper.insert(appointmentDO);
        for (AppointmentItemDO item : appointmentItems.addAppointmentId(appointmentDO.getId())) {
            appointmentItemMapper.insert(item);
        }
    }

    //排队编号 A001
    private String getNumber(Long barberId) {
        BarberDO barber = barberService.getBarber(barberId);
        return numberService.createNumber(barber.getCode(), barberId, LocalDate.now());
    }

    public PageResult<AppointmentListDO> getAppointmentPage(AppointmentForm appointmentForm) {
        Page<AppointmentListDO> list = appointmentMapper.listPage(appointmentForm.page(), appointmentForm);
        return new PageResult<>(list.getRecords(), list.getTotal());
    }


    @Transactional
    public void orderFinish(Long id, PayTypeEnum payType) {
        AppointmentDO appointmentDO = this.finish(payType, id, null);
        if (PayTypeEnum.INTEGRAL == payType) {
            pointsService.addRecord(id, appointmentDO.getIntegral(), appointmentDO.getOpenid(), "-");
            customerService.updateIntegral(appointmentDO.getOpenid(), appointmentDO.getMoney());
            return;
        }
        if (PayTypeEnum.BALANCE == payType) {
            pointsService.addRecord(id, appointmentDO.getIntegral(), appointmentDO.getOpenid(), "+");
            customerService.updateIntegral2(appointmentDO.getOpenid(), appointmentDO.getMoney(), appointmentDO.getIntegral());
            walletService.addRecord(appointmentDO.getMoney(), "-", appointmentDO.getId(), appointmentDO.getOpenid());
            return;
        }
        pointsService.addRecord(id, appointmentDO.getIntegral(), appointmentDO.getOpenid(), "+");
        customerService.updateIntegral3(appointmentDO.getOpenid(), appointmentDO.getIntegral());
    }


    public List<AppointmentStatictis> getAppointmentStatic(LocalDateTime start, LocalDateTime end) {
        return appointmentMapper.statictis(start, end);
    }


    public Long getWaitCount(Long appointmenId) {
        QueryWrapper<AppointmentDO> query = new QueryWrapper<>();
        query.eq("status", AppointmentEnum.WAIT.name());
        query.eq("hair_date", LocalDate.now());
        query.lt("id", appointmenId);
        return appointmentMapper.selectCount(query);
    }

    public void barbering(Long appointmentId) {
        AppointmentDO appointment = this.getAppointment(appointmentId);
        if (appointment == null || appointment.getStatus() != AppointmentEnum.WAIT) {
            return;
        }
        appointment.barbering();
        updateAppointment(appointment);
    }

    public void cancel(Long appointmentId) {
        AppointmentDO appointment = this.getAppointment(appointmentId);
        if (appointment == null || appointment.getStatus() != AppointmentEnum.WAIT) {
            return;
        }
        appointment.cancel();
        updateAppointment(appointment);
    }

    public void unpaid(Long appointmentId) {
        AppointmentDO appointment = this.getAppointment(appointmentId);
        if (appointment == null || appointment.getStatus() != AppointmentEnum.BARBERING) {
            return;
        }
        appointment.unpaid();
        updateAppointment(appointment);
    }

    public AppointmentDO finish(PayTypeEnum payType, Long appointmentId, String flowNo) {
        AppointmentDO appointment = this.getAppointment(appointmentId);
        appointment.finish(payType, flowNo);
        updateAppointment(appointment);
        return appointment;
    }

    //处理昨天没有完成的订单，自动设置超时
    @Scheduled(cron = "0 */1 * * * ?")
    @TenantIgnore
    public void handleLastDayUnFinsh() {
        QueryWrapper<AppointmentDO> query = new QueryWrapper<>();
        query.in("status",AppointmentEnum.WAIT,AppointmentEnum.BARBERING);
        query.eq("hair_date",LocalDate.now().plusDays(-1));
        List<AppointmentDO> appointmentDOS = this.appointmentMapper.selectList(query);
        for (AppointmentDO appointmentDO : appointmentDOS) {
            updateAppointment(appointmentDO.setStatus(AppointmentEnum.TIMEOUT));
        }
    }
}
